#ifndef __TCPTUNNELCREATOR_H__
#define __TCPTUNNELCREATOR_H__

#include "TcpTunnel.h"

namespace encrypted
{
    namespace tunnel
    {

// TcpTunnelBuilder is a builder pattern(design pattern) of tcp tunnel.
// Can be replaced by another builder(such as UdpTunnelBuilder).
// Accept tcp connections and connect to another service (such as Polipo when running as server, or the service provider when running as client)
// Build two TcpTunnel(one is for accepted connection to connected connection, another is for connected connection to accepted connection)
        template<typename CryptoFactoryPtr, typename SendProxyPtr>
        class TcpTunnelBuilder
        {
        public:
            TcpTunnelBuilder(boost::asio::io_service& io_service,
                             boost::asio::ip::tcp::endpoint endpoint,
                             std::string remotehost,
                             std::string remoteport,
                             CryptoFactoryPtr cryptoFactory, SendProxyPtr sendProxy)
                : acceptor(io_service),
                  endpoint(endpoint),
                  remotehost(remotehost),
                  remoteport(remoteport),
                  cryptoFactory(cryptoFactory),
                  sendProxy(sendProxy)
            {
            }

            ~TcpTunnelBuilder()
            {
            }

            // Listen to the port and accept connection asynchornize
            void start()
            {
                acceptor.open(endpoint.protocol());
                acceptor.bind(endpoint);
                acceptor.listen();
                TcpSocketPtr client(new TcpSocket(acceptor.get_io_service()));

                acceptor.async_accept(*client, boost::bind(&TcpTunnelBuilder::accepted, this, client, _1));
            }

        private:
            // Callback.  Will be called when a connection accepted
            void accepted(TcpSocketPtr client, const boost::system::error_code& error)
            {
                if(!error)
                {
                    boost::shared_ptr<boost::asio::ip::tcp::resolver>
                        resolver(new boost::asio::ip::tcp::resolver(acceptor.get_io_service()));

                    resolver->async_resolve(boost::asio::ip::tcp::resolver_query(remotehost, remoteport),
                                            boost::bind(&TcpTunnelBuilder::resolved, this, resolver, client, _1, _2));
                }
                else
                    std::cerr << "WARN:accepted:" << error << ':' << error.message() << std::endl;

                client = TcpSocketPtr(new TcpSocket(acceptor.get_io_service()));

                // Asynchronize accept another connection from client again.
                acceptor.async_accept(*client, boost::bind(&TcpTunnelBuilder::accepted, this, client, _1));

            }

            // Callback.  Will be called when resolved a dns, then connect to the resolved IP
            void resolved(boost::shared_ptr<boost::asio::ip::tcp::resolver> resolver,
                          TcpSocketPtr client,
                          const boost::system::error_code& error,
                          boost::asio::ip::tcp::resolver::iterator iterator)
            {
                if(error)
                {
                    std::cerr << "WARN:resolved:" << error << ':' << error.message() << std::endl;
                    return;
                }
                TcpSocketPtr server(new TcpSocket(acceptor.get_io_service()));

                // Asynchornize connect to another service.
                server->async_connect(iterator->endpoint(), \
                                      boost::bind(&TcpTunnelBuilder::connected, this, client, server, iterator, _1));
            }

            // Callback.  Will be called when connect to the resolved IP
            void connected(TcpSocketPtr client,
                           TcpSocketPtr server,
                           boost::asio::ip::tcp::resolver::iterator iterator,
                           const boost::system::error_code& error)
            {
                if(error)
                {
                    ++iterator;
                    if(iterator == boost::asio::ip::tcp::resolver::iterator())
                    {
                        std::cerr << "WARN:connected:" << error << ':' << error.message() << std::endl;
                        return;
                    }
                    resolved(boost::shared_ptr<boost::asio::ip::tcp::resolver>(), \
                             client, boost::system::error_code(), iterator);
                    return;
                }

                // Create 2 tunnels,
                // one is for client to service(Polipo) data transfer tunnel,
                // another is for service to client data transfer tunnel.


                (new TcpTunnel<typename CryptoFactoryPtr::element_type::DecryptorPtr, SendProxyPtr>(client, server, cryptoFactory->createDecryptor(), sendProxy))->start();
                (new TcpTunnel<typename CryptoFactoryPtr::element_type::EncryptorPtr, SendProxyPtr>(server, client, cryptoFactory->createEncryptor(), sendProxy))->start();
            }

            boost::asio::ip::tcp::acceptor acceptor;
            boost::asio::ip::tcp::endpoint endpoint;
            std::string remotehost, remoteport;
            CryptoFactoryPtr cryptoFactory;
            SendProxyPtr sendProxy;
        };
    }
}
#endif
